/*
 * 
 * ....................................................................
 * ...######..#####.....##....####..#####..####..#####..######....##...
 * .....##....##..#....###...##.....##.....##....##..#....##.....###...
 * .....##....####....#.##....##....####...####..####.....##....#.##...
 * .....##....##.#...#####.....##...##.....##....##.#.....##...#####...
 * .....##....##..#.#...##..####....##.....####..##..#....##..#...##...
 * ....................................................................
 */

package ua.trasferta.common.utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;

import ua.trasferta.common.annotations.createBy;
import ua.trasferta.common.collections.Maps;
import ua.trasferta.common.collections.Sets;
import static ua.trasferta.common.team.TeamMember.*;

/**
 * <p>
 * Java utility class for processing common <>reflection</b> operations.<br>
 * </p>
 * 
 * @author Anton Podviaznikov
 */
@createBy(authors = ANTON_PODVIAZNIKOV)
public class Reflections
{
	/**
	 * Private constructor.
	 */
	private Reflections()
	{
		// hidden constructor.
	}

	/**
	 * Method finds {@link Map} of the fields which was annotated with {@code
	 * annotation}.
	 * 
	 * @param <T> - concrete type of the {@link Annotation}.
	 * @param clazz - {@link Class} which fields should be extracted.
	 * @param annotation - {@link Annotation} which support should be found.
	 * @return {@link Map} with the pair {@link Field}- {@link Annotation}.
	 */
	public static <T extends Annotation> Map<Field, T> findMapOfAnnotatedFields(Class<?> clazz, Class<T> annotation)
	{
		Map<Field, T> annotatedFields = Maps.newLinkedHashMap();
		if (clazz != null)
		{
			Field[] fields = clazz.getDeclaredFields();
			if (fields != null)
			{
				for (Field field : fields)
				{
					if (field.getAnnotation(annotation) != null)
					{
						annotatedFields.put(field, field.getAnnotation(annotation));
					}
				}
			}
		}
		return annotatedFields;
	}

	/**
	 * Method finds all fields annotated with the specified annotations.
	 * 
	 * @param <T> - annotation class.
	 * @param clazz
	 * @param annotation - annotation class.
	 * @return
	 */
	public static <T extends Annotation> Set<Field> findSetOfAnnotatedFields(Class<?> clazz, Class<T> annotation)
	{
		Set<Field> annotatedFields = Sets.newLinkedHashSet();
		if (clazz != null)
		{
			Field[] fields = clazz.getDeclaredFields();
			if (fields != null)
			{
				for (Field field : fields)
				{
					if (field.getAnnotation(annotation) != null)
					{
						annotatedFields.add(field);
					}
				}
			}
		}
		return annotatedFields;
	}

	/**
	 * Method finds all fields annotated with the specified annotations.
	 * 
	 * @param <T> - annotation class.
	 * @param clazz
	 * @param annotation - annotation class.
	 * @return
	 */
	public static Set<String> findFieldsNames(Class<?> clazz)
	{
		Set<String> names = Sets.newLinkedHashSet();
		if (clazz != null)
		{
			Field[] fields = clazz.getDeclaredFields();
			if (fields != null)
			{
				for (Field field : fields)
				{
					names.add(Strings.strToCamelCase(field.getName()));
				}
			}
		}
		return names;
	}

	@SuppressWarnings("unchecked")
	public static <T> T get(Field field, Object target) throws Exception
	{
		boolean accessible = field.isAccessible();
		try
		{
			field.setAccessible(true);
			return (T)field.get(target);
		}
		catch (IllegalArgumentException iae)
		{
			String message = "Could not get field value by reflection: " + field + " on: " + target.getClass().getName();
			throw new IllegalArgumentException(message, iae);
		}
		finally
		{
			field.setAccessible(accessible);
		}
	}

	public static void set(Field field, Object target, Object value) throws Exception
	{
		try
		{
			field.set(target, value);
		}
		catch (IllegalArgumentException iae)
		{
			// target may be null if field is static so use
			// field.getDeclaringClass() instead
			String message =
					"Could not set field value by reflection: " + field + " on: " + field.getDeclaringClass().getName();
			if (value == null)
			{
				message += " with null value";
			}
			else
			{
				message += " with value: " + value.getClass();
			}
			throw new IllegalArgumentException(message, iae);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> T getAndWrap(Field field, Object target)
	{
		boolean accessible = field.isAccessible();
		try
		{
			field.setAccessible(true);
			return (T)Reflections.get(field, target);
		}
		catch (Exception e)
		{
			if (e instanceof RuntimeException)
			{
				throw (RuntimeException)e;
			}
			else
			{
				throw new IllegalArgumentException("exception setting: " + field.getName(), e);
			}
		}
		finally
		{
			field.setAccessible(accessible);
		}
	}

	public static void setAndWrap(Field field, Object target, Object value)
	{
		boolean accessible = field.isAccessible();
		try
		{
			field.setAccessible(true);
			Reflections.set(field, target, value);
		}
		catch (Exception e)
		{
			if (e instanceof RuntimeException)
			{
				throw (RuntimeException)e;
			}
			else
			{
				throw new IllegalArgumentException("exception setting: " + field.getName(), e);
			}
		}
		finally
		{
			field.setAccessible(accessible);
		}
	}
}
